package com.flyqiu.flow.core.handler;

import com.alibaba.fastjson2.JSONObject;
import com.flyqiu.common.bean.FlyQiuBeanManage;
import com.flyqiu.common.transaction.FlyQiuTransactionManage;
import com.flyqiu.flow.api.context.ProcInstContext;
import com.flyqiu.flow.api.element.FlyQiuFlowElementManage;

import com.flyqiu.flow.api.script.FlyQiuFlowScriptActuatorManage;
import com.flyqiu.flow.api.script.IVariableScope;
import com.flyqiu.flow.api.service.IdentityLinkService;
import com.flyqiu.flow.api.service.base.FlowVariableService;
import com.flyqiu.flow.api.std.ElementActivityHandler;
import com.flyqiu.flow.api.std.ElementStartHandler;
import com.flyqiu.flow.bpmn.element.ProcessModel;
import com.flyqiu.flow.bpmn.element.SequenceFlow;

import com.flyqiu.flow.common.std.ActivityHandlerResult;
import com.flyqiu.flow.core.tools.ProcSequenceCompute;
import com.flyqiu.flow.core.var.VariableScope;
import com.flyqiu.flow.event.ActInstDefaultEventContext;
import com.flyqiu.flow.event.ActInstEvent;
import com.flyqiu.flow.event.ActInstEventContext;
import com.flyqiu.flow.event.api.FlyQiuFlowEventManage;
import com.flyqiu.flow.mapper.ProcHiActivityMapper;
import com.flyqiu.flow.mapper.ProcRuActivityMapper;
import com.flyqiu.flow.bpmn.element.BaseProcElement;

import com.flyqiu.flow.model.entity.ProcDeployDef;
import com.flyqiu.flow.model.entity.base.Activity;
import com.flyqiu.flow.model.entity.base.Variable;
import com.flyqiu.flow.model.entity.hi.ProcHiActivity;
import com.flyqiu.flow.model.entity.ru.ProcRuActivity;
import com.flyqiu.flow.model.entity.ru.ProcRuInst;


import java.time.LocalDateTime;
import java.util.List;


public abstract class BaseProcActElementHandler<T extends BaseProcElement, R> implements ElementActivityHandler<R> {

    protected T currentElement;
    protected ProcInstContext procContext;
    protected ProcDeployDef procDeployDef;
    protected String currentDefKey;

    protected FlyQiuFlowEventManage procEventManage;

    protected FlyQiuTransactionManage transactionManage;
    protected ProcRuActivityMapper ruActivityMapper;
    protected ProcHiActivityMapper hiActivityMapper;
    private ActivityDispatchEvent dispatchEvent;

    protected FlyQiuFlowElementManage procElementManage;
    protected FlyQiuBeanManage procBeanManage;
    protected FlyQiuFlowScriptActuatorManage scriptManage;


    protected IdentityLinkService identityLinkService;
    protected FlowVariableService flowVariableService;
    private Boolean isInitBean = false;
    private Boolean isInitBaseData = false;

    private void initBaseBean(ProcInstContext procContext) {
        if (isInitBean) {
            return;
        }

        this.procContext = procContext;
        procBeanManage = procContext.getGlobalContext().getBeanManage();
        scriptManage = procContext.getGlobalContext().getFlowScriptManage();
        transactionManage = procContext.getGlobalContext().getTransactionManage();
        procEventManage = procContext.getGlobalContext().getFlowEventManage();

        procElementManage = procContext.getGlobalContext().getFlowElementManage();

        hiActivityMapper = procBeanManage.getMapper(ProcHiActivityMapper.class);
        ruActivityMapper = procBeanManage.getMapper(ProcRuActivityMapper.class);
        identityLinkService = procBeanManage.getBean(IdentityLinkService.class);
        flowVariableService = procBeanManage.getBean(FlowVariableService.class);
        dispatchEvent = new ActivityDispatchEvent();
        initBean(procBeanManage);


        this.isInitBean = true;
    }

    private void initBaseData(String currentDefKey) {
        if (isInitBaseData) {
            return;
        }
        this.currentDefKey = currentDefKey;
        this.currentElement = getCurrentElement();
        procDeployDef = procContext.getProcDeployDef();
        this.isInitBaseData = true;
    }

    protected void initBean(FlyQiuBeanManage procBeanManage) {

    }

    @Override
    public final R start(ProcInstContext procContext, String currentDefKey) {
        initBaseBean(procContext);
        initBaseData(currentDefKey);
        ProcRuActivity procActivity = getProcActivity(procContext.getProcInst());
        if (procActivity == null) {
            return null;
        }
        ActInstEventContext eventContext = createEventContext(procActivity);
        dispatchEvent.createBefore(eventContext);

        ruActivityMapper.insert(procActivity);
        ActivityHandlerResult<R> activityHandlerResult = startElementInst(procContext.getProcInst(), procActivity);

        procActivity.setAssignee(activityHandlerResult.getAssignee());
        procActivity.setAssigneeKey(activityHandlerResult.getAssigneeKey());
        procActivity.setAssigneeAttr(activityHandlerResult.getAssigneeAttr());
        procActivity.setAssigneeNickName(activityHandlerResult.getAssigneeNickName());
        procActivity.setAssigneeUniqueKey(activityHandlerResult.getAssigneeUniqueKey());


        procActivity.setActInstId(activityHandlerResult.getActTypeInstId());
        ruActivityMapper.updateById(procActivity);
        ProcHiActivity procHiActivity = ProcHiActivity.create(procActivity);
        procHiActivity.setEndTime(null);
        hiActivityMapper.insert(procHiActivity);
        dispatchEvent.createAfter(eventContext);

        if (activityHandlerResult.isAutoNext()) {
            complete(procContext, procActivity.getId());
        }
        return activityHandlerResult.getOtherEntity();
    }

    private ActInstEventContext createEventContext(Activity activity) {
        return new ActInstDefaultEventContext(procDeployDef, procContext.getProcInst(), currentElement, activity);
    }


    /**
     * 完成活动
     *
     * @param procContext
     * @param actId
     */
    @Override
    public final void complete(ProcInstContext procContext, String actId) {
        initBaseBean(procContext);
        ProcRuActivity activity = ruActivityMapper.selectById(actId);
        complete(procContext, activity);
    }

    @Override
    public final void complete(ProcInstContext procContext, ProcRuActivity activity) {
        initBaseBean(procContext);
        initBaseData(activity.getDefKey());
        VariableScope activityVariables = attachActVar(activity);
        IVariableScope procVariables = procContext.getProcVariables();
        procVariables.append(activityVariables);

        ActInstEventContext eventContext = createEventContext(activity);
        dispatchEvent.completeBefore(eventContext);
        if (!completeElementInst(procContext, activity)) {
            procVariables.remove(activityVariables);
            return;
        }
        ProcHiActivity hiActivity = createHiActivity(activity);
        hiActivityMapper.updateById(hiActivity);
        ruActivityMapper.deleteById(activity.getId());
        dispatchEvent.completeAfter(eventContext);

        startNextActivity(procContext, activity);
        identityLinkService.moveToHistory(activity.getProcInstId(), activity.getId());
        flowVariableService.moveToHistory(activity.getProcInstId(), activity.getId());
        procVariables.remove(activityVariables);

    }

    private VariableScope attachActVar(ProcRuActivity activity) {
        List<Variable> procRuVariables = flowVariableService.listRuVariable(activity.getProcInstId(), activity.getId()).stream().map(e -> (Variable) e).toList();
        return new VariableScope(procContext.getGlobalContext().getFlowVarSerializeManage(), procRuVariables);
    }

    /**
     * 完成元素实例
     *
     * @param procContext
     * @param ruActivity
     * @return boolean true 继续执行活动 false停止执行下一活动
     */
    protected boolean completeElementInst(ProcInstContext procContext, ProcRuActivity ruActivity) {
        return true;
    }

    /**
     * 启动下一个活动
     *
     * @param procContext
     * @param currentRuActivity
     */
    protected void startNextActivity(ProcInstContext procContext, ProcRuActivity currentRuActivity) {
        ProcSequenceCompute nextProcSequenceCompute = ProcSequenceCompute.compute(scriptManage, procContext.getCurProcessModel(), currentRuActivity.getDefKey(), procContext.getProcVariables());
        SequenceFlow sequenceFlow = nextProcSequenceCompute.getFinalSingle();
        FlyQiuFlowElementManage procElementConfig = procContext.getGlobalContext().getFlowElementManage();
        ElementStartHandler<?> actInst = procElementConfig.createStartInst(sequenceFlow.getElementType());
        actInst.start(procContext, sequenceFlow.getDefKey());


    }


    /**
     * 启动元素实例
     *
     * @param currentProcInst 当前流程实例
     * @param ruActivity      当前活动信息
     * @return
     */
    protected ActivityHandlerResult<R> startElementInst(ProcRuInst currentProcInst, ProcRuActivity ruActivity) {
        ActivityHandlerResult<R> objectActivityHandlerResult = new ActivityHandlerResult<>("");
        objectActivityHandlerResult.setAutoNext(true);
        return objectActivityHandlerResult;
    }

    protected ProcRuActivity getProcActivity(ProcRuInst procRuInst) {
        T element = getCurrentElement();
        ProcRuActivity activity = create(element);
        activity.setProcInstId(procRuInst.getId());
        activity.setTenantId(procRuInst.getTenantId());
        return activity;
    }

    protected ProcHiActivity createHiActivity(ProcRuActivity ruActivity) {
        ProcHiActivity procHiActivity = ProcHiActivity.create(ruActivity);
        procHiActivity.completeEndTime(LocalDateTime.now());
        return procHiActivity;
    }

    public static ProcRuActivity create(BaseProcElement element) {
        ProcRuActivity result = new ProcRuActivity();
        result.setActType(element.getElementType());
        result.setDefKey(element.getDefKey());
        result.setDefName(element.getDefName());
        result.setAssignee("");
        result.setAssigneeKey("");
        result.setCreateTime(LocalDateTime.now());
        return result;
    }
    /**
     * 初始化活动
     */
    protected T getCurrentElement() {
        if (currentElement != null) {
            return currentElement;
        }
        ProcessModel curProcessModel = procContext.getCurProcessModel();
        currentElement = curProcessModel.getElement(this.currentDefKey);
        return currentElement;
    }

    private class ActivityDispatchEvent implements ActInstEvent {
        @Override
        public void createBefore(ActInstEventContext eventContext) {
            procEventManage.triggerCreateBeforeEvent(ActInstEvent.class, eventContext);
        }

        @Override
        public void createAfter(ActInstEventContext eventContext) {
            procEventManage.triggerCreateAfterEvent(ActInstEvent.class, eventContext);
        }

        @Override
        public void completeBefore(ActInstEventContext eventContext) {
            procEventManage.triggerCompleteBeforeEvent(ActInstEvent.class, eventContext);
        }

        @Override
        public void completeAfter(ActInstEventContext eventContext) {
            procEventManage.triggerCompleteAfterEvent(ActInstEvent.class, eventContext);
        }

        @Override
        public void deleteBefore(ActInstEventContext eventContext, JSONObject params, String reason) {
            procEventManage.triggerDeleteBeforeEvent(ActInstEvent.class, eventContext, params, reason);
        }

        @Override
        public void deleteAfter(ActInstEventContext eventContext, JSONObject params, String reason) {
            procEventManage.triggerDeleteAfterEvent(ActInstEvent.class, eventContext, params, reason);
        }
    }
}
